// Copyright Gary Evans 2006-2007.

using System;
using System.Diagnostics;

namespace Taumuon.Jabuka.Physics
{
    /// <summary>
    /// A rigid body that updates its location
    ///  via Euler integration.
    /// </summary>
    public class EulerRigidBody : IEulerRigidBody
    {
        #region Member Variables

        private Vector location = null;
        private Vector velocity = null;
        private Vector force = null;
        private double mass = 0.0;

        #endregion Member Variables

        #region Object Lifetime

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="location">Initial location.</param>
        /// <param name="velocity">Initial velocity.</param>
        /// <param name="mass">The body's mass.</param>
        /// <exception cref="ArgumentException">
        /// if mass is zero
        /// </exception>
        /// <exception cref="ArgumentNullException"/>
        public EulerRigidBody(Vector location, Vector velocity, double mass)
        {
            if (mass == 0.0)
            {
                throw new ArgumentException("mass cannot be zero");
            }
            if (location == null)
            {
                throw new ArgumentNullException(
                    "location");
            }
            if (velocity == null)
            {
                throw new ArgumentNullException(
                    "velocity");
            }
            this.location = location;
            this.velocity = velocity;
            this.mass = mass;
        }

        #endregion Object Lifetime

        #region Public Accessors

        /// <summary>
        /// Retrieves the current location.
        /// </summary>
        public Vector Location
        {
            get
            {
                return this.location;
            }
        }

        /// <summary>
        /// Retrieves the current velocity.
        /// </summary>
        public Vector Velocity
        {
            get
            {
                return this.velocity;
            }
        }

		/// <summary>
		/// Retrieves the current acceleration.
		///  This is the body's force (if any)
		///  divided by the body's mass.
		/// </summary>
		public Vector Acceleration
		{
			get
			{
				if (this.force == null)
				{
					this.force = new Vector(0.0, 0.0, 0.0);
				}
				return this.force * (1.0 / mass);
			}
		}

		/// <summary>
		/// Retrieves the current force being applied to the object.
		/// </summary>
		public Vector AppliedForce
		{
			get
			{
				return this.force;
			}
		}

        /// <summary>
        /// Returns the object's mass.
        /// </summary>
        public double Mass
        {
            get
            {
                return this.mass;
            }
        }

        #endregion Public Accessors

        #region Public Methods

        /// <summary>
        /// Allows the body to update its physical state.
        /// </summary>
        /// <param name="deltaTime">Time increment, in seconds.</param>
        public void Update(double deltaTime)
        {
            this.velocity += this.Acceleration * deltaTime;
            this.location += this.velocity * deltaTime;
        }

        /// <summary>
        /// Applies a force to the body.
        /// </summary>
        /// <param name="force">Force, in Newtons.</param>
        public void ApplyForce(Vector force)
        {
            Debug.Assert(!double.IsInfinity(this.mass),
                "Should not ApplyForce() to an object of infinite mass");
            this.force = force;
        }

		/// <summary>
		/// Assigns the values of this EulerRigidBody and the values of its members
		///  from the values on the EulerRigidBody passed in.
		/// </summary>
		/// <param name="rhs">The EulerRigidBody to set the values from</param>
		/// <remarks>This sets all values, analogous to a deep copy.
		/// It does not change any of the original objects' or sub objects' references
		///  (only sets values on those references)</remarks>
		public void AssignFromEulerRigidBody(IEulerRigidBody rhs)
		{
			this.location.SetValues(rhs.Location.X, rhs.Location.Y, rhs.Location.Z);
			this.velocity.SetValues(rhs.Velocity.X, rhs.Velocity.Y, rhs.Velocity.Z);
			if (this.force == null)
			{
				if (rhs.AppliedForce != null)
				{
					this.force = rhs.AppliedForce.Clone() as Vector;
				}
			}
			else
			{
				if (rhs.AppliedForce == null)
				{
					this.force = null;
				}
				else
				{
					this.force.SetValues(rhs.AppliedForce.X, rhs.AppliedForce.Y, rhs.AppliedForce.Z);
				}
			}

			this.mass = rhs.Mass;
		}

        #endregion Public Methods

        #region ICloneable Members

        /// <summary>
        /// Performs a deep copy.
        /// </summary>
        /// <returns>A copy of the object.</returns>
        public object Clone()
        {
            EulerRigidBody clone = this.MemberwiseClone() as EulerRigidBody;
            clone.location = this.location.Clone() as Vector;
            clone.velocity = this.velocity.Clone() as Vector;
            clone.force = this.force == null ? null : this.force.Clone() as Vector;
            return clone;
        }

        #endregion ICloneable Members
    }
}
